function VideoViewer(varargin)
% General Purpose Video Player
%
% Usage: 
%  VideoViever('Argument',Value,...);
%  
% All arguments are passed as argument-value pairs.
%
% Arguments:
%  Data                : Video data in matlab format 
%                        ( Pixels X x Pixels Y x Color Channels x Frames )
%  File                  : Load Data from a File 
%                        (only mat (Frames/Time, movf) & dat (raw uint8) supported)
%  Time                : Time in seconds 
%                        ( Timesteps X 1 )
%  Transpose [0]       : Transpose video (Rotate by 90 degree);
%  MirrorX [0]         : Mirror video horizontally
%  MirrorY [0]         : Mirror video vertically
%  CMin [0]            : Minimum of color range
%  CMax [255]          : Maximum of color range
%  TrueLimits [0]      : Compute true color range limits (slower startup)
%  BackRange [ ]       : Averaging range for background subtraction
%  StartFrame [1]      : Frame to start on
%  CurvesByFrame [ ]   : Curves to display for each frame 
%                        (for adding annotations)
%  FIG [1]             : Number of figure to plot into. 
% 
% Mouse Actions : 
%   Frame Window : 
%    Left          : Zoom to point
%    Right         : Set Marker, e.g. Touch
%    Shift + Left  : Set Platform Edge
%   Color Bar : 
%    Left and Right click, set the limits of the color bar
% 
% Keyboard Shortcuts :
%
% Playing: 
% - Space : Play / Pause
% - Arrow left :  one frame back
% - Arrow right :  one frame forward
% - Arrow up : beginning of movie
% - Arrow down : end of movie
% - r : change between frame-by-frame and real playback speed
%
% Zooming:
% - 1,2,3,4 : zoom a quadrant
% - Esc/0 : zoom out again
%
% Exporting
% - q : set start of export range to current frame
% - w : set end of export range to current frame 
% - f : export Frame
% - e : export Movie within limits
% - h : export movf Matlab movie
% - m : export as AVI Movie file (location : Desktop)
% - s : save Annotations (also is done automatically on closing the window)
% 
% Visual: 
% - c : change colormaps
% - g : show grid
% - b : activate / inactivate background subtraction
% - d : delete the annotations made for the current frame
% 
% Author : benglitz@gmail.com
%
% TODO: 
% - Animal tracking ()
% - multiple windows open possible
% - load Controller data automatically by RecID

%% PROCESS ARGUMENTS
P =  parsePairs(varargin);
checkField(P,'Data',[]);  
checkField(P,'File',[]);
checkField(P,'Transpose',0);
checkField(P,'MirrorX',0);
checkField(P,'MirrorY',0);
checkField(P,'Time',[]);
checkField(P,'FrameRate',[]);
checkField(P,'CMin',0);
checkField(P,'CMax',255);
checkField(P,'TrueLimits',0);
checkField(P,'BackRange',[1:50]);
checkField(P,'StartFrame',1);
checkField(P,'CurvesByFrame',[]);
checkField(P,'Marker','x');
checkField(P,'FIG',1);
checkField(P);

try delete(P.FIG); end

global VV; VV = []; 
if isempty(P.Data)  VV_loadData(P.File); if ~isfield(VV,'Video') return; end; 
else VV.Video = P.Data;  end
P = rmfield(P,'Data'); VV.P = P;

if P.Transpose VV.Video = permute(VV.Video,[2,1,3,4]); end
if P.MirrorX VV.Video = flipdim(VV.Video,2); end
if P.MirrorY VV.Video = flipdim(VV.Video,1); end

VV.NFrames = size(VV.Video,4);
VV.Dims(1) = size(VV.Video,1);
VV.Dims(2) = size(VV.Video,2);
VV.CurrentFrame = P.StartFrame;
VV.PlaySpeed = 1; 
VV.RealSpeed = 0;
VV.SequenceOfInspection = zeros(100000,2);
VV.CurrentInspection = 1;
VV.FIG=P.FIG;
if isempty(P.CurvesByFrame) VV.CurvesByFrame = cell(VV.NFrames,1); end

%% PROCESS BACKGROUND AVERAGE
VV.P.BackRange = [VV.P.BackRange(1) : min([VV.P.BackRange(2),VV.NFrames])];
VV_computeBackground;
VV.Background = 0;

if VV.P.TrueLimits
  VV.Limits = double([min(VV.Video(:)),max(VV.Video(:))]);
else
  VV.Limits = [VV.P.CMin,VV.P.CMax]; 
  VV.LimitsNorm = VV.Limits;
end

%% PROCESS TIME
if ~isfield(VV,'Time')
  if isempty(VV.P.Time)   VV.Time = [1:VV.NFrames];
    if ~isempty(VV.P.FrameRate) VV.Time = VV.Time/VV.P.FrameRate; end
  else 
    VV.Time = VV.P.Time;
  end
end
% CHECK TIME FORMAT
SizeT = size(VV.Time); Pos = find(SizeT==VV.NFrames);
if isempty(Pos) error('Time Vector does not have the same length as the Frames.');
else if Pos==2 VV.Time = VV.Time'; end; VV.Time = VV.Time(:,1); end
VV.Time(:,1) = VV.Time(:,1) - VV.Time(1,1);

%% PREPRARE FIGURE (COMPUTE SIZES)
SS = get(0,'ScreenSize');
FigWidth = round(0.6*SS(4));
RelWidth = 0.86; RelHeight = 0.92;
FrameWidth = RelWidth*FigWidth;
FrameHeight = FrameWidth/VV.Dims(2) * VV.Dims(1);
ControlHeight = 200;
FigHeight = FrameHeight + ControlHeight;
FigHeight = round(FigHeight/RelHeight);
FrameRelHeight = FrameHeight/FigHeight/RelHeight;
ControlRelHeight = ControlHeight/FigHeight;
FigSize = [FigWidth,FigHeight];
ControlRelHeights = [0.15,0.2,0.2,0.2]; SepY = [0.15,0.2,0.15,0.3];
Normalizer = (sum(ControlRelHeight) + sum(SepY))/RelHeight;
DivY = [FrameRelHeight,ControlRelHeight*ControlRelHeights/Normalizer]; 
SepY = ControlRelHeight*SepY./Normalizer;

figure(P.FIG); set(P.FIG,'Position',[50,50,FigSize],'Toolbar','figure','Name','VideoViewer','NumberTitle','off','DeleteFcn',{@VV_saveData});
set(P.FIG,'KeyPressFcn',{@LF_KeyPress});
DC = axesDivide(1,DivY,[0.07,0.03,RelWidth,RelHeight],[],SepY);

%% CURRENT IMAGE
VV.AH.CurrentFrame = axes('Position',DC{1}); 
VV.FrameRate = roundSign(1/median(diff(VV.Time(:))),3);
VV.GUI.IH = imagesc(VV.Video(:,:,1,P.StartFrame)); hold on;
set(VV.GUI.IH,'HitTest','off');
set(gca,'XTick',[0:100:VV.Dims(2)],'YTick',[0:100:VV.Dims(1)],'PlotBoxAspectRatio',[VV.Dims([2,1])./max(VV.Dims),1],'PlotBoxAspectRatioMode','manual');
title([...
  'Resolution : ',num2str(VV.Dims(2)),'x',num2str(VV.Dims(1)),...
  '     NFrames : ',num2str(VV.NFrames),...
  '     FrameRate : ',num2str(VV.FrameRate),'Hz',...
  '     Duration : ',num2str(VV.Time(end)),'s']);
set(VV.AH.CurrentFrame,'ButtonDownFcn',{@VV_mainFrame})

%% COLORBAR & COLOR CONTROLS
DC2 = axesDivide([0.8,0.1,0.1,0.25],1,DC{2},[0.05],[]);
VV.AH.Colorbar = axes('Position',DC2{1});
VV.NColSteps = 256; colormap(gray(VV.NColSteps)); VV.Colormap =0;
VV.CLimits = VV.Limits; caxis(VV.AH.CurrentFrame,VV.CLimits); 
VV.GUI.CIH = imagesc(1); set(VV.GUI.CIH,'Hittest','off');
VV.GUI.CLimMin = uicontrol('style','edit','String',VV.CLimits(1),...
  'Units','normalized','Position',DC2{2},'Callback',{@VV_setCLimit,'setedit'},'Tooltip','Change Color Min');
VV.GUI.CLimMax = uicontrol('style','edit','String',VV.CLimits(2),...
  'Units','normalized','Position',DC2{3},'Callback',{@VV_setCLimit,'setedit'},'Tooltip','Change Color Max');
set(VV.AH.Colorbar,'ButtonDownFcn',{@VV_setCLimit,'setvisual'})
VV.GUI.BackRange = uicontrol('style','edit','String',HF_list2colon(VV.P.BackRange),...
  'Units','normalized','Position',DC2{4},'Callback',{@VV_setBackRange,'setedit'},'Tooltip','Set Background Subtraction Range');

%% FRAME SUBSET
VV.AH.FrameSubset = axes('Position',DC{3});
VV_showFrameSubset;

VV_setCLimit;

%% FRAME CHOOSER & INTERFRAME TIMES
VV.AH.FrameChooser = axes('Position',DC{4});
%set(gca,'XTick',[0:50:VV.NFrames],'XTickLabel',[0:50:VV.NFrames]);
box on; hold on;
VV.FrameChooserMax = 1;
if ~isempty(VV.Time)
  VV.FrameTimes = diff(VV.Time);
  FrameTimes = plot(VV.FrameTimes*1000); 
  set(FrameTimes,'HitTest','off'); 
  ylabel(VV.AH.FrameChooser,'\DeltaT [ms]');
  VV.FrameChooserMax = max(diff(VV.Time))*1000;
  %VV.FrameChooserMax = 10*prctile(diff(VV.Time),95)*1000;
end
VV.GUI.CurrentLine = plot([VV.CurrentFrame,VV.CurrentFrame],[0,VV.FrameChooserMax],'Color','r','HitTest','off'); 
VV.GUI.AllAnnotations = plot([-1],[-1],'.r'); set(VV.GUI.AllAnnotations,'HitTest','off'); 
  
axis([0,VV.NFrames,0,VV.FrameChooserMax]); 
XTicks = C_autobin(1,VV.NFrames,10);
set(VV.AH.FrameChooser,'ButtonDownFcn',{@VV_scrobblor},'XTick',[1,XTicks(2:end)]);
set(P.FIG,'WindowButtonUpFcn','global Scrobbling_ ; Scrobbling_ = 0;'),
xlabel('Frames');

%% CONTROLS
DC2 = axesDivide([0.12,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.1,0.12,0.12],1,DC{5},[0.05],[]);
VV.GUI.CurrentFrame = uicontrol('style','edit','String',num2str(VV.CurrentFrame),...
  'Units','normalized','Position',DC2{1},'Callback',{@VV_showFrame,'setedit'},'Tooltip','Current Frame');
VV.GUI.PlaySpeed = uicontrol('style','edit','String',num2str(VV.PlaySpeed),...
  'Units','normalized','Position',DC2{2},'Tooltip','Playback Speed');
VV.GUI.Help = uicontrol('style','pushbutton','String','Doc',...
  'Units','normalized','Position',DC2{3},'Tooltip','Show Documentation','Callback','doc VideoViewer');

ButtonStyles = {'pushbutton','pushbutton','pushbutton','togglebutton','togglebutton','pushbutton','pushbutton','pushbutton','togglebutton'};
Commands = {'Set','Change','Change','Play','Play','Change','Change','Set',''};
Strings = {'|<<','-10','-1','<','>','+1','+10','>>|','R'};
Tooltips = {'Jump to Beginning','Jump 10 Frames','Jump 1 Frame','Play Backward',...
  'Play Forward','Jump 1 Frame','Jump 10 Frames','Jump To End','Repeat'};
W = [1,1,1];
Colors = {0.5*W,0.6*W,0.7*W,0.9*W,0.9*W,0.7*W,0.6*W,0.5*W,0.9*W};
for i=1:length(Strings)
  UH(i) = uicontrol('style',ButtonStyles{i},'String',Strings{i},'Units','normalized','Position',DC2{3+i},...
    'Callback',{@VV_showFrame,Commands{i},Strings{i}},'BackGroundColor',Colors{i},'Tooltip',Tooltips{i},'KeypressFcn',@LF_KeyPress);
end
VV.GUI.PlayForward = UH(5);
VV.GUI.PlayBackward = UH(4);
VV.GUI.Repeat = UH(end);

VV.GUI.StartFrame = uicontrol('style','edit','String',num2str(1),...
  'Units','normalized','Position',DC2{end-1},'Callback',{@VV_setLimit,'start'},'Tooltip','Start of Selection');
VV.GUI.StopFrame = uicontrol('style','edit','String',num2str(VV.NFrames),...
  'Units','normalized','Position',DC2{end},'Callback',{@VV_setLimit,'end'},'Tooltip','End of Selection');

%% CALLBACK FUNCTIONS
function VV_mainFrame(O,E)
global VV;

SelType = get(VV.FIG,'SelectionType');
switch SelType
  case 'normal'; VV_zoomFrame(O,E);
  case 'alt'; VV_recordLocation(O,E);
  case 'extend'; VV_drawPlatform(O,E);
end

function VV_zoomFrame(O,E)
global VV;
Point = get(O,'CurrentPoint');
Point = Point(1,1:2);
set(VV.AH.CurrentFrame,...
  'XLim',[Point(1) - VV.Dims(2)/4,Point(1)+VV.Dims(2)/4],...
  'YLim',[Point(2) - VV.Dims(1)/4,Point(2)+VV.Dims(1)/4]);

function VV_recordLocation(O,E,Opt)
global VV;
if ~exist('Opt','var') Opt = ''; end
switch Opt
  case 'refresh';
  otherwise
    Point = get(O,'CurrentPoint');
    Point = Point(1,1:2);
    VV.CurvesByFrame{VV.CurrentFrame}{end+1} = {Point(1),Point(2),now};
end
XData = find(~cellfun(@isempty,VV.CurvesByFrame));
set(VV.GUI.AllAnnotations,'XData',XData,'YData',zeros(size(XData)) + 1);
VV_showFrame(O,E,'redraw');

function VV_showFrameSubset
  global VV;
  Ind1 = round(linspace(1,VV.Dims(1),50));
  Ind2 = round(linspace(1,VV.Dims(2),50));
  IndT = round(linspace(1,VV.NFrames,12));
  FrameSelection = squeeze(VV.Video(Ind1,Ind2,1,IndT));
  if VV.Background
    FrameSelection = int16(FrameSelection) - repmat(VV.VideoBackground(Ind1,Ind2),[1,1,length(IndT)]);
  end
  FrameSelection(:,end+1:end+2,:) = VV.Limits(end);
  FrameSelection = reshape(FrameSelection,[length(Ind1),(length(Ind2)+2)*length(IndT)]);
  if ~isfield(VV.GUI,'FH')
    VV.GUI.FH = imagesc(FrameSelection(:,1:end-2));
  else 
    set(VV.GUI.FH,'CData',FrameSelection(:,1:end-2));
  end
  set(VV.AH.FrameSubset,'XTick',[],'YTick',[]);

function LF_KeyPress(handle,event,FID)

global VV
CC = get(VV.FIG,'CurrentCharacter');
if ~isempty(CC)
  switch int8(CC)
    case 28; VV_showFrame([],[],'change','-1');  % right
    case 29; VV_showFrame([],[],'change','+1'); % left 
    case 31; VV_showFrame([],[],'set',VV.NFrames); % down
    case 30; VV_showFrame([],[],'set',1); % up
    case 98; VV_setBackground;
    case 100; VV.CurvesByFrame{VV.CurrentFrame} = {}; VV_recordLocation([],[],'refresh'); VV_showFrame([],[],'redraw'); 
    case 101; VV_exportMovie([],[],'movie'); %e export movie
    case 102; VV_exportMovie([],[],'frame'); %f export frame
    case 104; VV_exportMovie([],[],'movf'); %g export movie
    case 109; VV_exportMovie([],[],'avi'); %m export avi movie
    case 103; State = get(VV.AH.CurrentFrame,'XGrid'); % g
      if strcmp(State,'on') State = 'off'; else State = 'on'; end;
      set(VV.AH.CurrentFrame,'XGrid',State,'YGrid',State);
    case 114; VV.RealSpeed = ~VV.RealSpeed; 
      set([VV.GUI.PlayForward,VV.GUI.PlayBackward],'ForegroundColor',[VV.RealSpeed,0,0]);
      disp(['Setting real speed playback to ',num2str(VV.RealSpeed)])% r : real speed
    case 113; set(VV.GUI.StartFrame,'String',num2str(VV.CurrentFrame)); VV_setLimit(VV.GUI.StartFrame,[],'start');
    case 119; set(VV.GUI.StopFrame,'String',num2str(VV.CurrentFrame)); VV_setLimit(VV.GUI.StopFrame,[],'end');
    case 115; VV_saveData;% s save annotations before quitting 
    case {48,27}; set(VV.AH.CurrentFrame,'XLim',[1,VV.Dims(2)],'YLim',[1,VV.Dims(1)]); % esc/0  = zoom out.
    case 49; set(VV.AH.CurrentFrame,'XLim',[1,VV.Dims(2)/2],'YLim',[1,VV.Dims(1)/2]); % 1 = Zoom Quadrant 1
    case 50; set(VV.AH.CurrentFrame,'XLim',[VV.Dims(2)/2+1,VV.Dims(2)],'YLim',[1,VV.Dims(1)/2]); % 2 = Zoom Quadrant 2
    case 51; set(VV.AH.CurrentFrame,'XLim',[1,VV.Dims(2)/2],'YLim',[VV.Dims(1)/2+1,VV.Dims(1)]); % 3 = Zoom Quadrant 3
    case 52; set(VV.AH.CurrentFrame,'XLim',[VV.Dims(2)/2+1,VV.Dims(2)],'YLim',[VV.Dims(1)/2+1,VV.Dims(1)]); % 4 = Zoom Quadrant 4
    case 32;  % space = play
      switch get(VV.GUI.PlayForward,'Value')
        case 0; set(VV.GUI.PlayForward,'Value',1); VV_showFrame([],[],'play','>');
        case 1; set(VV.GUI.PlayForward,'Value',0);
      end
    case 99; % c = colormap change 
      axes(VV.AH.CurrentFrame);
      if ~VV.Colormap colormap(jet(VV.NColSteps)); VV.Colormap=1;
      else colormap(gray(VV.NColSteps)); VV.Colormap=0;
      end
  end
end

function VV_drawPlatform(O,E)
  global VV;

  Point = get(O,'CurrentPoint');
  Point = Point(1,1:2);
  if ~isfield(VV.GUI,'Platform')
    VV.GUI.Platform = plot([Point(1),Point(1)],[1,VV.Dims(2)],'g');
  else
    set(VV.GUI.Platform,'XData',[Point(1),Point(1)]);
  end
  VV.Platform.Position = Point(1);

  
function VV_setBackRange(O,E,Ind)
global VV;
    
VV.P.BackRange = eval(get(O,'String'));
VV.recomputeBackground = 1;
  
 function VV_computeBackground;
 global VV;
 
tmp = single(VV.Video(:,:,:,VV.P.BackRange));
VV.VideoBackground = int16(mean(tmp,4));
VV.recomputeBackground = 0;

function VV_setBackground;

  global VV;

  if VV.recomputeBackground
    VV_computeBackground;
  end
  
  if VV.Background % CURRENT STATE
    disp('Adding Background...'); 
    VV.Background = 0; % NEW STATE
  else
    disp('Subtracting Background...');
    VV.LimitsNorm = VV.Limits;
    VV.Background = 1;
  end
  if VV.Background
    VV.Limits = [-128,128];
    VV.CLimits = [-10,10];
  else
    VV.Limits = VV.LimitsNorm;
    VV.CLimits = VV.Limits;
  end
  VV_showFrame([],[],'redraw')
  VV_showFrameSubset;
  VV_setCLimit;
  
function VV_exportMovie(O,E,Opt)
 global VV;
 
  switch Opt
    case {'movie','avi','movf'};
      StartFrame =  str2num(get(VV.GUI.StartFrame,'String'));
      StopFrame =  str2num(get(VV.GUI.StopFrame,'String'));
      disp(['Frames ',num2str(StartFrame),' to ',num2str(StopFrame),' exported to base workspace.']);
    case 'frame';
      StartFrame = VV.CurrentFrame;
      StopFrame = StartFrame;
      disp(['Frame ',num2str(StartFrame),' exported to variable "Exported" in base workspace.']);
      
    otherwise
  end
 
  cData = VV.Video(:,:,:,StartFrame:StopFrame);
  switch Opt
    case 'avi';
      Filename = input('Specify Filename for Movie: ','s');
      V = VideoWriter(['~/Desktop/',Filename]); 
      V.FrameRate = 30; open(V);
      NFrames = StopFrame - StartFrame+1;
      disp('Writing File....');
      writeVideo(V,(single(VV.Video(:,:,:,StartFrame:StopFrame))-VV.Limits(1))/diff(VV.Limits));      

    case 'movf';
      movf = struct('cdata',[],'colormap',[]);
     for iF=1:size(cData,4)
       movf(iF).cdata = repmat(cData(:,:,iF),[1,1,3]);
     end
     cData = movf;
  end
  assignin('base','Exported',cData);  
  
function VV_setCLimit(O,E,Opt,CLimits)
global VV;
if ~exist('Opt','var') Opt = ''; end

switch Opt
  case 'set'; VV.CLimits = CLimits;
  case 'setedit'; 
    if O==VV.GUI.CLimMin LimInd = 1; else LimInd = 2; end 
    VV.CLimits(LimInd) = str2num(get(O,'String'));
  case 'setvisual';
    NewLimit = get(VV.AH.Colorbar,'CurrentPoint');
    NewLimit = round(NewLimit(1));
    SelType = get(VV.FIG,'SelectionType');
    switch SelType
      case 'normal'; VV.CLimits(1) = NewLimit;
      case 'alt'; VV.CLimits(2) = NewLimit;
    end
  otherwise % for empty case
end

VV.CLimits = sort(VV.CLimits);
CX = linspace(VV.Limits(1),VV.Limits(2),VV.NColSteps);
NBelow =  round(VV.NColSteps*(VV.CLimits(1)-VV.Limits(1))/diff(VV.Limits));
NAbove = round(VV.NColSteps*(VV.Limits(2)-VV.CLimits(2))/diff(VV.Limits));;
CY = [zeros(1,NBelow) , linspace(0,1,VV.NColSteps-NBelow-NAbove) , ones(1,NAbove)] ;
set(VV.GUI.CIH,'CData',CY,'XData',CX); caxis(VV.AH.Colorbar,[0,1]);
set(VV.AH.Colorbar,'XLim',CX([1,end]),'YTick',[]);
caxis(VV.AH.CurrentFrame,VV.CLimits);
caxis(VV.AH.FrameSubset,VV.CLimits);
set(VV.GUI.CLimMin,'String',num2str(VV.CLimits(1)));
set(VV.GUI.CLimMax,'String',num2str(VV.CLimits(2)));

function VV_setLimit(O,E,Opt)
  
global VV

cLimit = str2num(get(O,'String'));
switch Opt
  case 'start'; 
    try delete(VV.GUI.Limits(1)); end
    VV.GUI.Limits(1) = plot(VV.AH.FrameChooser,[cLimit,cLimit],[0,VV.FrameChooserMax],'Color',[0,1,0],'LineWidth',2);
  case 'end';  
    try delete(VV.GUI.Limits(2)); end
    VV.GUI.Limits(2) = plot(VV.AH.FrameChooser,[cLimit,cLimit],[0,VV.FrameChooserMax],'Color',[1,0,0],'LineWidth',2);
end

function VV_showFrame(O,E,Command,iFrame)
global VV

switch lower(Command)
  case 'set'; 
    switch iFrame
      case '|<<'; iFrame = 1;
      case '>>|'; iFrame = VV.NFrames;
    end
    VV.CurrentFrame = iFrame;

  case 'setedit'; VV.CurrentFrame= str2num(get(O,'String'));
  case 'setvisual'; VV.CurrentFrame = get(O,'CurrentPoint');VV.CurrentFrame = round(VV.CurrentFrame(1,1));
  case 'change';   VV.CurrentFrame = eval(['VV.CurrentFrame',iFrame]); 
  case 'play'; 
    switch iFrame
      case '>'; CheckVal = VV.NFrames;
      case '<'; CheckVal = 1;
    end
    if isempty(O) O = VV.GUI.PlayForward; end
    while get(O,'Value') && VV.CurrentFrame~=CheckVal && VV.CurrentFrame <VV.NFrames+1 && VV.CurrentFrame>-1
      tic; 
      cData = VV.Video(:,:,1,VV.CurrentFrame);
      if VV.Background cData = int16(cData) - VV.VideoBackground; end
      set(VV.GUI.IH,'CData',cData);
      set(VV.GUI.CurrentFrame,'String',num2str(VV.CurrentFrame));
      set(VV.GUI.CurrentLine,'XData',[VV.CurrentFrame,VV.CurrentFrame]);
      pause(0.001); StepSize = str2num(get(VV.GUI.PlaySpeed,'String'));
      Repeat = get(VV.GUI.Repeat,'Value');
      TimePerFrame = toc;
      if VV.RealSpeed 
        StepSize = find(cumsum(VV.FrameTimes(VV.CurrentFrame:end))>TimePerFrame,1,'first');
        if isempty(StepSize) StepSize = 1; end
      end
      switch iFrame
        case '>'; 
          VV.CurrentFrame = VV.CurrentFrame+StepSize;
          if VV.CurrentFrame >=VV.NFrames; 
            if Repeat
              VV.CurrentFrame = 1;
            else
              set(O,'Value',0);
            end
          end
           
        case '<';
          VV.CurrentFrame = VV.CurrentFrame-StepSize;
          if VV.CurrentFrame <=1; 
            if Repeat
              VV.CurrentFrame = VV.NFrames;
            else
              set(O,'Value',0);
            end
          end
      end
    end
  case 'redraw';
end

VV.CurrentFrame = min([VV.NFrames,VV.CurrentFrame]);
VV.CurrentFrame= max([1,VV.CurrentFrame]);

cData = VV.Video(:,:,1,VV.CurrentFrame);
if VV.Background cData = int16(cData) - VV.VideoBackground; end

set(VV.GUI.IH,'CData',cData);
set(VV.GUI.CurrentFrame,'String',num2str(VV.CurrentFrame));
set(VV.GUI.CurrentLine,'XData',[VV.CurrentFrame,VV.CurrentFrame]);
 
%% SHOW OTHER INFORMATION (WHISKER FITS / CONTACTS)
try delete(VV.LastPH); end
VV.LastPH = [];
cCurves = VV.CurvesByFrame{VV.CurrentFrame};
if ~isempty(cCurves)
  for i=1:length(cCurves)
    VV.LastPH(end+1) = plot(VV.AH.CurrentFrame,cCurves{i}{1},cCurves{i}{2},VV.P.Marker,'Color','r','MarkerSize',12);
  end
end
VV.SequenceOfInspection(VV.CurrentInspection,[1:2]) = [now,VV.CurrentFrame];
VV.CurrentInspection = VV.CurrentInspection + 1;

function VV_loadData(File);
  
  global VV
  if isempty(File)
    [FileNameIn,PathName] = uigetfile([pwd,filesep,'*.*']);
    if FileNameIn==0 return; end
    cd(PathName);
    VV.FileNameIn = [PathName,FileNameIn];
    if isempty(FileNameIn) || isnumeric(FileNameIn) && FileNameIn == 0; return; end
  else 
    VV.FileNameIn = File;
  end
  FileSpec = dir(VV.FileNameIn);
  FileType = VV.FileNameIn(find(VV.FileNameIn=='.')+1:end);
  VV.FileNameAnnotations = [VV.FileNameIn(1:end-length(FileType)+1),'_Annotations.mat'];
  switch FileType
    case 'mat'; 
      D = load(VV.FileNameIn);
      if isfield(D,'Data')
        VV.Time = D.Data.Time;
        if isfield(D.Data,'Frames')
          VV.Video = D.Data.Frames;
        else
          FileNameFrames = [VV.FileNameIn(1:end-3),'dat'];
          FID = fopen(FileNameFrames,'r');
          TMP = fread(FID,prod(D.Data.Dims),'*uint8');
          VV.Video = reshape(TMP,D.Data.Dims);
          fclose(FID);
        end
      elseif isfield(D,'movf')
        if isstruct(D.movf)
          for iF=1:length(D.movf)
            VV.Video(:,:,1,iF) = D.movf(iF).cdata(:,:,1);
          end
        else
          VV.Video = D.movf;
        end
        NFrames = size(VV.Video,4);
        VV.Time = [1:NFrames]/300;
      end

    case 'dat';
      Resolution = [320,240];
      BytesPerFrame = Resolution(1)*Resolution(2);
      NFrames = FileSpec.bytes/BytesPerFrame;
      FID = fopen(VV.FileNameIn,'r');
      FramesPerLoad = 5000;
      FramesToLoad = NFrames; k = 0;
      disp([num2str(FramesToLoad),' Frames to Load : '])
      VV.Video = zeros([Resolution,1,NFrames],'uint8');
      while FramesToLoad>FramesPerLoad
        k=k+1;
        fprintf([num2str(FramesPerLoad * k),' ']);
        D = fread(FID,FramesPerLoad*BytesPerFrame,'*uint8');
        cFrames = (k-1)*FramesPerLoad+1:k*FramesPerLoad;
        VV.Video(:,:,:,cFrames) = reshape(D,[Resolution(1),Resolution(2),1,length(D)/BytesPerFrame]);
        FramesToLoad = FramesToLoad - FramesPerLoad;
        clear D;
      end
      D = fread(FID,FramesToLoad*BytesPerFrame,'*uint8');
      cFrames = k*FramesPerLoad+1:k*FramesPerLoad+FramesToLoad;
      VV.Video(:,:,:,cFrames) = reshape(D,[Resolution(1),Resolution(2),1,length(D)/BytesPerFrame]);
      VV.Time = [1:NFrames]'/300;
  end
 
function VV_saveData(O,E)
    global VV
    if isempty(VV) return; end
    R.CurvesByFrame = VV.CurvesByFrame;
    R.SequenceOfInspection = VV.SequenceOfInspection(1:VV.CurrentInspection,:);
    AnnotationsExist = sum(~cellfun(@isempty,R.CurvesByFrame));
    
    if AnnotationsExist
      if ~isfield(VV,'FileNameAnnotations')
        VV.FileNameAnnotations = input('Enter Filename for Annotations : ','s');
        if isempty(VV.FileNameAnnotations) return; end
        VV.FileNameAnnotations = [pwd,VV.FileNameAnnotations];
      end
      if isfield(VV,'FileNameAnnotations')
        disp(['Saving to ',VV.FileNameAnnotations]);
        save(VV.FileNameAnnotations,'-struct','R');
      end
    end
    
 
    
function VV_scrobblor(O,E)

  global VV;
  
  AxPos = get(VV.AH.FrameChooser,'Position');
  FigPos = get(VV.FIG,'Position');
  Pixels = AxPos(3)*FigPos(3);
  NFrames = VV.NFrames;
  FramesPerPixel = NFrames/Pixels;
  AxisStartPixels =  round(FigPos(1) + AxPos(1)*FigPos(3));

  SelType = get(VV.FIG,'SelectionType');
  switch SelType
    case 'normal';
      global Scrobbling_ ; Scrobbling_ =1;
      
      while Scrobbling_
        CurrentPoint = get(0,'PointerLocation');
        CurrentFrame  = round(FramesPerPixel*(CurrentPoint(1)-AxisStartPixels));
        VV_showFrame([],[],'set',CurrentFrame);
        pause(0.001);
      end
      
    case {'alt','extend'};
      CurrentPoint = get(0,'PointerLocation');
      CurrentFrame  = round(FramesPerPixel*(CurrentPoint(1)-AxisStartPixels));
      switch SelType
        case 'alt'; Command = 'start';  O = VV.GUI.StartFrame;
        case 'extend'; Command = 'end'; O = VV.GUI.StopFrame;
      end
      set(O,'String',num2str(CurrentFrame));
      VV_setLimit(O,E,Command);
  end